package com.leyou.search.service;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.HighlightUtils;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.pojo.*;
import com.leyou.search.dto.GoodDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.dto.SearchResult;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.SearchRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private SearchRepository searchRepository;//增删改
    @Autowired
    private ElasticsearchTemplate esTemplate;//用于查询

    @Autowired
    private ItemClient itemClient;

    /**
     * 索引库导入（MySQL->ES）
     */
    public void importData(){
        int page = 1;
        int rows = 100;
        long totalPage = 1;
        //1.分页查询Spu数据

        do {
            PageResult<SpuDTO> pageResult = itemClient.spuPageQuery(
                    page, rows, null, true);//注意：导入到ES的数据必须是上架的

            //取出当前页的数据
            List<SpuDTO> spuDTOList = pageResult.getItems();

            if(CollectionUtils.isNotEmpty(spuDTOList)){
                List<Goods> goodsList = spuDTOList.stream().map( spuDTO -> buildGoods(spuDTO)  ).collect(Collectors.toList());

                //批量保存数据到ES中
                searchRepository.saveAll(goodsList);
            }

            page++;
            totalPage = pageResult.getTotalPage();//总页数
        }while (page<=totalPage);
    }

    /**
     * 把一个SpuDTO封装成一个Goods
     */
    public Goods buildGoods(SpuDTO spuDTO){
        Goods goods = new Goods();

        goods.setId(spuDTO.getId());
        goods.setSpuName(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCreateTime(new Date().getTime());

        //1.根据spuId查询所有Sku对象
        List<Sku> skuList = itemClient.findSkusBySpuId(spuDTO.getId());

        if(CollectionUtils.isNotEmpty(skuList)){

            List<Map<String,Object>> skuMapList = new ArrayList<>();

            skuList.forEach(sku -> {
                Map<String,Object> skuMap = new HashMap<>();
                skuMap.put("id",sku.getId());
                skuMap.put("images",sku.getImages());
                skuMap.put("price",sku.getPrice());
                skuMapList.add(skuMap);
            });
            //转换json字符串
            String skuJson = JsonUtils.toString(skuMapList);

            String all = spuDTO.getName()+" "+spuDTO.getSubTitle()+" "+skuList.stream().map(sku -> sku.getTitle()).collect(Collectors.joining(" "));

            List<Long> price = skuList.stream().map(sku -> sku.getPrice()).collect(Collectors.toList());

            goods.setSkus(skuJson);
            goods.setAll(all);
            goods.setPrice(price);

        }

        /**
         *  Map集合存储的内容
         *      key: 规格参数的名称  tb_spec_param表
         *      value：规格参数值   tb_spu_detail表
         */
        //1）查询出当前分类下的searching=true的规格参数
        List<SpecParam> specParams = itemClient.findSpecParams(null, goods.getCategoryId(), true);

        //2）根据spuId查询SpuDetail对象
        SpuDetail spuDetail = itemClient.findSpuDetailBySpuId(spuDTO.getId());

        //3）取出generic_spec和special_spec
        String genericSpec = spuDetail.getGenericSpec();
        String specialSpec = spuDetail.getSpecialSpec();

        //4）判断参数是否为通用参数（generic），如果是通用参数从generic_spec取值，如果特有参数从special_spec取值

        Map<String,Object> specs = new HashMap<>();

        if(CollectionUtils.isNotEmpty(specParams)){

            specParams.forEach(specParam -> {
                String key = specParam.getName();

                Object value = null;

                //判断参数是否为通用参数
                if(specParam.getGeneric()){
                    //通用参数，从generic_spec取值
                    //把generic_spec转换为Map集合
                    //toMap(): 转换简单Map集合
                    Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpec, Long.class, Object.class);
                    //从Map中取值
                    value = genericSpecMap.get(specParam.getId());
                }else{
                    //特有参数，从special_spec取值
                    //nativeRead: 转换复杂的Map集合
                    Map<Long,List<Object>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<Object>>>() {});
                    value = specialSpecMap.get(specParam.getId());
                }

                //把数字类型的参数从普通值转换为范围，如 5.0  -> 4.5-5.0英寸
                if(specParam.getNumeric()){
                    //转换为范围
                    value = chooseSegment(value,specParam);
                }

                specs.put(key,value);
            });

        }

        goods.setSpecs(specs);

        return goods;
    }

    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    public SearchResult<GoodDTO> goodsSearch(SearchRequest searchRequest) {
        //1.创建SearchResult对象
        SearchResult<GoodDTO> searchResult = new SearchResult<GoodDTO>();

        //2.封装SearchResult对象
        //1）分页条件查询商品
        PageResult<GoodDTO> pageResult = itemQueryPage(searchRequest);

        //2）查询搜索过滤条件
        Map<String,Object> filterConditions = filterConditionsQuery(searchRequest);

        searchResult.setItems(pageResult.getItems());
        searchResult.setTotal(pageResult.getTotal());
        searchResult.setTotalPage(pageResult.getTotalPage());
        searchResult.setFilterConditions(filterConditions);

        //3.返回SearchResult对象
        return searchResult;
    }

    /**
     * 查询搜索过滤条件
     *         Map<String,Object> fi
     * @param searchRequest
     * @return
     */
    private Map<String, Object> filterConditionsQuery(SearchRequest searchRequest) {
        //注意：LinkedHashMap有序的Map集合
        Map<String, Object> filterConditions = new LinkedHashMap<>();

        //1.创建查询
        NativeSearchQueryBuilder queryBuilder = buildSearchQueryBuilder(searchRequest);
        //2.添加聚合条件 注意：创建聚合条件，使用AggregationBuilders工具类
        String categoryAgg = "categoryAgg";
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms( categoryAgg  ).field("categoryId")  );
        queryBuilder.addAggregation(AggregationBuilders.terms( brandAgg  ).field("brandId")  );

        NativeSearchQuery searchQuery = queryBuilder.build();

        //2.执行聚合查询，获取结果
        //AggregatedPage: 可以封装聚合条件
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(searchQuery,Goods.class);

        //3.处理结果，返回
        Aggregations aggregations = aggregatedPage.getAggregations();

        //3.1 处理分类结果
        Terms categoryTerms = aggregations.get(categoryAgg);
        List<Long> categoryIdList = categoryTerms
                                        .getBuckets()
                                        .stream()
                                        .map(bucket->((Terms.Bucket) bucket).getKeyAsNumber())
                                        .map(number -> number.longValue())
                                        .collect(Collectors.toList());
        //根据分类ID集合查询分类对象集合
        List<Category> categoryList = itemClient.findCategoriesByIds(categoryIdList);

        filterConditions.put("分类",categoryList);

        //3.2 处理品牌结果
        Terms brandTerms = aggregations.get(brandAgg);
        List<Long> brandIdList = brandTerms
                .getBuckets()
                .stream()
                .map(bucket->((Terms.Bucket) bucket).getKeyAsNumber())
                .map(number -> number.longValue())
                .collect(Collectors.toList());
        //根据分类ID集合查询分类对象集合
        List<Brand> brandList = itemClient.findBrandsByIds(brandIdList);

        filterConditions.put("品牌",brandList);


        //4.处理规格参数的过滤条件显示

        if(categoryIdList!=null){
            categoryIdList.forEach(categoryId->{

                //1）查询所有分类下的用于搜索过滤条件的参数
                List<SpecParam> specParams = itemClient.findSpecParams(null, categoryId, true);

                //2）添加聚合条件
                if(specParams!=null){
                    specParams.forEach(specParam -> {
                        queryBuilder.addAggregation( AggregationBuilders.terms(specParam.getName()).field("specs."+specParam.getName()+".keyword") );
                    });
                }

                //3）进行聚合查询
                NativeSearchQuery specParamSearchQuery = queryBuilder.build();
                AggregatedPage<Goods> specAggPage = esTemplate.queryForPage(specParamSearchQuery,Goods.class);

                //4）获取参数的聚合结果
                Aggregations specAggs = specAggPage.getAggregations();

                if(specParams!=null){
                    specParams.forEach(specParam -> {
                        Terms specTerms = specAggs.get(specParam.getName());

                        List<Object> specKeys = specTerms.getBuckets()
                                                    .stream()
                                                    .map(bucket->((Terms.Bucket) bucket).getKey())
                                                    .collect(Collectors.toList());


                        filterConditions.put(specParam.getName(),specKeys);
                    });
                }

            });
        }


        return filterConditions;
    }

    /**
     * 分页条件查询商品
     * @param searchRequest
     * @return
     */
    private PageResult<GoodDTO> itemQueryPage(SearchRequest searchRequest) {
        //1.创建SearchQuery对象（SearchQuery：用于封装所有查询条件）
        NativeSearchQueryBuilder queryBuilder = buildSearchQueryBuilder(searchRequest);

        //2）分页条件
        /**
         * of(page,size)  page：页码(page从0开始计算)， size: 每页记录数
         */
        queryBuilder.withPageable(PageRequest.of( searchRequest.getPage()-1 , searchRequest.getSize()  ) );

        //3）结果过滤条件
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","spuName","subTitle","skus"},null));

        //4）高亮字段
        /*HighlightBuilder.Field field = new HighlightBuilder.Field("spuName");
        field.preTags("<font color='red'>");
        field.postTags("</font>");
        queryBuilder.withHighlightFields(field);*/

        HighlightUtils.highlightField(queryBuilder,"spuName");

        NativeSearchQuery searchQuery = queryBuilder.build();

        //2.执行查询，获取结果
        //Page: 存放了分页查询结果，类似IPage对象
        Page<Goods> pageBean = esTemplate.queryForPage(searchQuery,Goods.class,HighlightUtils.highlightBody(Goods.class,"spuName"));

        //3.处理结果并返回
        //1）取出所有Goods对象
        List<Goods> goodsList = pageBean.getContent();
        //2）数据拷贝
        List<GoodDTO> goodDTOList = BeanHelper.copyWithCollection(goodsList,GoodDTO.class);
        //3）封装PageResult
        PageResult<GoodDTO> pageResult = new PageResult<GoodDTO>(pageBean.getTotalElements(),Long.valueOf(pageBean.getTotalPages()),goodDTOList);
        return pageResult;
    }

    private NativeSearchQueryBuilder buildSearchQueryBuilder(SearchRequest searchRequest) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //1）Query查询条件， 注意：创建Query查询条件，使用QueryBuilders工具类
        //1.1 创建布尔查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1.2 在布尔查询里面添加must条件
        //boolQueryBuilder.must( QueryBuilders.matchQuery("all",searchRequest.getKey())  );
        boolQueryBuilder.must( QueryBuilders.multiMatchQuery(searchRequest.getKey(),"all","spuName") );

        /**
         * 添加搜索过滤条件的过滤
         */
        //1)接收过滤条件参数
        Map<String, Object> filterParams = searchRequest.getFilterParams();
        //2）遍历加入过滤
        if(filterParams!=null){
            filterParams.entrySet().forEach(entry->{
                String key = entry.getKey();
                Object value = entry.getValue();

                //处理key值
                if(key.equals("品牌")){
                    key = "brandId";
                }else if(key.equals("分类")){
                    key = "categoryId";
                }else{
                    key = "specs."+key+".keyword";
                }

                //3)添加过滤条件
                boolQueryBuilder.filter(  QueryBuilders.termQuery(key,value)  );
            });
        }


        //1.3 把布尔查询条件存入Query条件
        queryBuilder.withQuery( boolQueryBuilder  );
        return queryBuilder;
    }

    public List<GoodDTO> goodsSearchChange(SearchRequest searchRequest) {
        PageResult<GoodDTO> pageResult = itemQueryPage(searchRequest);
        return pageResult.getItems();
    }

    public void createIndex(Long id) {
        //1.根据id查询商品
        SpuDTO spuDTO = itemClient.findSpuById(id);
        //2.把SpuDTO转换为Goods
        Goods goods = buildGoods(spuDTO);
        //3.导入es索引库
        searchRepository.save(goods);
    }

    public void deleteIndex(Long id) {
        searchRepository.deleteById(id);
    }
}
